#!/usr/bin/python
'''
    Filename - ftpc.py
    Author: Gerrit Kitts <gkitts@gmail.com>    
    Usage: ftpc.py (remoteIP) (remotePort) (file-to-transfer)
    
    This package provides a very simple client that will transfer binary files to a host
    that is running it's sister process, ftps.py.  Communication is through simple streaming (TCP)
    socket.  The protocol used passes the file size as the first four bytes of the stream, 
    the basename (see os.path.basename) of the file as the next twenty bytes, and then the bytes
    representing the file to be transferred.
'''

import hashlib
import socket
import os, os.path
import struct

from sys import argv

'''
    Exception sub-class to handle invalid argument specifications
'''
class ArgumentException(BaseException):
    def __init__(self):
        self.msg = "Usage: ftps.py (remoteIP) (remotePort) (file-to-transfer)"

''' 
    openFile - Attempts to open the file provided as params['file'], and reads 
      the size and its basename.  
      Return: Returns a tuple containing the file handle, the size of the file 
        and the basename of the file
      Exceptions: If the file does not exist, raises an IOError.      
'''
def openFile(params):
    filename = params['file']
    if not os.path.exists(filename):
        raise IOError("File %s was not found!" % (filename))
    stats = os.stat(filename)
    size = stats.st_size    
    f = open(filename, 'rb')
    basename = os.path.basename(filename)
    return (f, size, basename)

'''
    Attempt to transfer the file to the server specified.  Reads
        params['address'] as the destination, and transfers the file
        in 1000 byte chunks.  
'''
def sendFile(params):
    fileProps = openFile(params)
    print "Sending file %s with size %i" % (fileProps[2], fileProps[1])
    
    # Establish a connection with the destination host, using IPV4 (AF_INET) 
    #   and TCP (SOCK_STREAM)
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    addr = params['address']
    s.connect(addr)
    
    # Pack the size and filename and send over the wire as binary data, 
    #   instead of relying on socket to translate the string to character data.
    packer = struct.Struct('! I 20s')
    packed_data = packer.pack(fileProps[1], fileProps[2])
    s.send(packed_data)
    
    # Now send the contents of the file in 1000 byte chunks.  For our purposes,
    #  a character is equivalent to a byte so we can read the file directly.    
    m = hashlib.md5()
    f = fileProps[0]    
    bytes = f.read(1000)
    m.update(bytes)
    while len(bytes) > 0:
        s.send(bytes)
        bytes = f.read(1000)
        m.update(bytes)
    
    # Close the socket to clean up after ourselves
    s.close()

    # Finally, determine the MD5 hash for easy comparison
    print "MD5 checksum is: %s" % (m.hexdigest())

'''
    Base wrapper for class functionality
'''
if __name__ == '__main__':
    params = {
        'address' : ('127.0.0.1', 7777),
        'file' : 'foo.jpg'
    }
    if (len(argv) is not 4):
        # Exception case
        raise ArgumentException 
    else:
        params['address'] = (argv[1], int(argv[2]))
        params['file'] = argv[3]

    sendFile(params)
    
